
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <signal.h>
#include <getopt.h>
#include <sys/mman.h>
#include <linux/ioctl.h>
#include <sys/errno.h>
#include <sys/ioctl.h>
#include <linux/types.h>
#include <linux/watchdog.h>
#include <linux/i2c-dev.h>
#include <linux/i2c.h>
#include <time.h>

#include "wdt.h"

#define WATCHDOG_DEV "/dev/watchdog"
#define WATCHDOG_TIME_LEFT "/sys/devices/platform/sbsa-gwdt.0/watchdog/watchdog0/timeleft"
#define WDT_BASE (0x28000000)
#define WDT_MAP_LENGTH (0xf000)
#define MAP_SIZE (0x100000)
#define WDT_START 0xab
#define WDT_STOP 0xba


static const char helpMsg[] =
    "Usage: \n"
    "        start      start watchdog, default set 175 seconds\n"
    "   feed time       feed hard watchdog, max set 175 seconds\n"
    "        status     show hard watchdog status\n"
    "        stop       stop hard watchdog\n"
    "";

/**
 * @brief 查询硬件狗的启动状态 
 * @return 1: wdt enabled, 0: wdt disabled
*/
static int get_wdt_enable()
{
    unsigned int pagesize = (unsigned)sysconf(_SC_PAGESIZE);
    uint32_t map_addr = 0x2800b000;;
    void *map_base, *virt_addr;
    uint32_t offset = 0;
    unsigned int test_val = 0;

    int fd = open("/dev/mem", O_RDWR | O_SYNC);
    if (fd == -1) {
        perror("/dev/mem");
        return 0;
    }

    map_base = mmap(0, MAP_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED,
                    fd, map_addr & ~((unsigned int)pagesize - 1));
    if (map_base == (void *)-1) {
        if (errno == EPERM) {
            printf("Is your kernel configured with CONFIG_DEVMEM_STRICT=n?\n");
            printf("Try rebooting and specifying iomem=relaxed on kernel command line.\n");
        }
        perror("cannot map SBREG");
        close(fd);
        return 0;
    }

    offset = (unsigned int)(map_addr & (pagesize - 1));
    virt_addr = map_base + offset;
    test_val = *((volatile uint32_t *)virt_addr);

    if (map_base) {
        munmap(map_base, MAP_SIZE);
    }
    close(fd);

    return test_val & 0x1;
}


/**
 * @brief 查询当前硬件狗剩余的重启时间
 * @return -1-failure or time_left
*/
static int get_wdt_time_left()
{
    FILE *fp;
    char buffer[100];
    int value = 0;

    fp = fopen(WATCHDOG_TIME_LEFT, "r");
    if (fp == NULL) {
        perror("Error opening file");
        return -1;
    }

    if (fgets(buffer, sizeof(buffer), fp) != NULL) {
        value = atoi(buffer);
    }

    fclose(fp);
    return value;
}

int wdt_load_driver()
{
    int iRet = system("modprobe sbsa_gwdt");
    if (iRet != 0) {
        return -1;
    }
    return 0;
}

int wdt_unload_driver()
{
    int iRet = system("rmmod sbsa_gwdt");
    if (iRet != 0) {
        return -1;
    }
    return 0;
}

int wdt_start()
{
    int fd, ret, time, opt = 0;

    // 打开文件句柄
    fd = open(WATCHDOG_DEV, O_WRONLY);
    if (fd < 0) {
        printf("open %s failure\n", WATCHDOG_DEV);
        return -2;
    }

    // 设置硬件狗重启时间
    time = MAX_WDT_TIME;
    ret = ioctl(fd, WDIOC_SETTIMEOUT, &time);
    if (ret != 0) {
        printf("Watchdog set timeout err %d.\n", ret);
        close(fd);
        return ret;
    }

    // 开启硬件狗
    opt = WDIOS_ENABLECARD;
    ret = ioctl(fd, WDIOC_SETOPTIONS, &opt);
    if (ret != 0) {
        printf("watchdog start err %d.\n", ret);
        close(fd);
        return ret;
    }

    close(fd);
    return 0;
}

int wdt_stop()
{
    int fd, ret = 0, opt = 0;
    fd = open(WATCHDOG_DEV, O_WRONLY);
    if (fd < 0) {
        printf("open %s failure\n", WATCHDOG_DEV);
        return -1;
    }

    opt = WDIOS_DISABLECARD;
    ret = ioctl(fd, WDIOC_SETOPTIONS, &opt);
    if (ret != 0) {
        printf("Watchdog stop err %d.\n", ret);
        close(fd);
        return -1;
    }

    close(fd);
    return 0;
}

int wdt_status()
{
    if (get_wdt_enable()) {
        return get_wdt_time_left();
    }
    return -1;
}

int wdt_feed(int time, int unit)
{
    int fd, ret;

    // 入参校验
    if (time <= 0 || time > MAX_WDT_TIME || unit != TIME_UNIT_SECONDS) {
        printf("param [%d-%d] invalid\n", time, unit);
        return -1;
    }

    // 打开文件句柄
    fd = open(WATCHDOG_DEV, O_WRONLY);
    if (fd < 0) {
        printf("open %s failure\n", WATCHDOG_DEV);
        return -2;
    }

    // 设置硬件狗重启时间
    ret = ioctl(fd, WDIOC_SETTIMEOUT, &time);
    if (ret != 0) {
        printf("Watchdog set timeout err %d.\n", ret);
        close(fd);
        return ret;
    }

    close(fd);
    return 0;
}

#ifdef __MAIN__

int main(int argc, char **argv)
{
    if (argc < 2) {
        printf("%s", helpMsg);
        return 1;
    }
    wdt_load_driver();

    if (strcmp(argv[1], "start") == 0) {
        if (wdt_start() == 0) {
            printf("watchdog start successful\n");
        } else {
            perror("watchdog start failure\n");
            return 1;
        }
    } else if (strcmp(argv[1], "feed") == 0 && argc == 3) {
        int time = atoi(argv[2]);
        if (time > MAX_WDT_TIME) {
            printf("Maximum time for feeding is %d seconds\n", MAX_WDT_TIME);
            return 1;
        } else {
            if (wdt_feed(time, TIME_UNIT_SECONDS) == 0) {
                printf("watchdog feed successful\n");
            } else {
                perror("watchdog feed failure\n");
                return 1;
            }
        }
    } else if (strcmp(argv[1], "status") == 0) {
        int status = wdt_status();
        if (status == -1) {
            printf("watchdog is disable\n");
        } else {
            printf("watchdog reboot time: [%d]\n", status);
        }
    } else if (strcmp(argv[1], "stop") == 0) {
        if (wdt_stop() == 0) {
            printf("watchdog stop successful\n");
        } else {
            perror("watchdog stop failure\n");
        }
    } else {
        printf("%s\n", helpMsg);
        return 1;
    }

    return 0;
}

#endif
